import cv2  
import time, logging
import pyzbar.pyzbar as pyzbar
import numpy as np
from PyQt5.Qt import QThread
from PyQt5 import QtCore
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtGui import QImage,QPixmap
import imutils, json
import math, easyocr, re

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)


kernel_2 = np.ones((2, 2), np.uint8)  # 2x2的卷积核
kernel_3 = np.ones((3, 3), np.uint8)  # 3x3的卷积核
kernel_4 = np.ones((4, 4), np.uint8)  # 4x4的卷积核
kernel_8 = np.ones((8, 8), np.uint8)

draw_color = (0,255,0)

Lower_red = np.array([0, 61, 136])    
Upper_red = np.array([5, 255, 255])
draw_red = draw_color
red = [Lower_red, Upper_red, 'red',draw_red]

Lower_blue = np.array([113, 97, 95])
Upper_blue = np.array([120, 189, 225])
draw_blue =draw_color
blue = [Lower_blue, Upper_blue, 'blue',draw_blue]

Lower_green = np.array([39, 60, 133])
Upper_green = np.array([96, 255, 255])
draw_green =draw_color
green = [Lower_green, Upper_green, 'green',draw_green]

Lower_yellow = np.array([21, 103, 63])   
Upper_yellow = np.array([37, 255, 255])
draw_yellow = draw_color
yellow = [Lower_yellow, Upper_yellow, 'yellow',draw_yellow]

Lower_black = np.array([70, 100, 100])
Upper_black = np.array([87, 150, 120])
draw_black = (0,0,0)
black = [Lower_black, Upper_black, 'black',draw_black]

color_list = [red,blue,green,yellow]


# 裁剪
# 宽度是x 顶点向右方向  高度是y ,顶点向下方向
crop_x_min = 0   
crop_x_max = 1000
crop_y_min = 590   
crop_y_max = 1890

class Visual_Class(QThread):
    cap_signal = pyqtSignal(str)

    def __init__(self,label,gui_signal):
        super().__init__()
        self.cap= None
        self.frame = None
        self.label = label
        self.stop = False

        self.program_flag = 0
        self.thresh_value = 0
        self.crop_value = 0
        self.Erosion_value = 0
        self.Dilation_value = 0

        self.P1 = None
        self.P2 = None
        self.P3 = None
        self.P4 = None
        self.P5 = None
        self.P6 = None
        self.P7 = None
        self.P8 = None
        self.P9 = None
        self.robot_Coordinate = []
        self.robot_Coordinate_str = ""

        self.gui_signal=gui_signal
        self.cap_signal.connect(self.cap_camera)

        self.position_init = {
                            "1":[],
                           "2":[],
                           "3":[],
                           "4":[],
                           "A":[],
                           "B":[],
                           "C":[],
                           "D":[],
                           
                           "red":[],
                           "blue":[],
                           "green":[],
                           "yellow":[],
                           "black":[],
                           "circle":[],
                           "triangle":[],
                           "rectangle":[],
                           "hexagon":[],
                           }
        self.position = self.position_init     
 
    #### 图像处理--形状识别,目标旋转角度
    def calculate_angle(self,approx):
        try:
            approx = approx.tolist()
            centroid_x = sum(vertex[0][0] for vertex in approx) / len(approx)
            centroid_y = sum(vertex[0][1] for vertex in approx) / len(approx)
            distances = [math.sqrt(math.pow(vertex[0][0] - centroid_x, 2) + math.pow(vertex[0][1] - centroid_y, 2)) for vertex in approx]
            max_distance = max(distances)
            max_index = distances.index(max_distance)
            angle_rad = math.atan2(approx[max_index][0][1] - centroid_y, approx[max_index][0][0] - centroid_x)
            angle_deg = math.degrees(angle_rad)
            return angle_deg
        except Exception as e:
            print("Error:", e)
            return None
    #### 图像处理--形状识别,目标旋转角度：三角形
    def triangle_angle(self, approx):
        angle_deg = self.calculate_angle(approx)
        if angle_deg is not None:
            print(f"origin deg is {angle_deg}")
            rema = angle_deg % 120
            print(f"div rema is {rema}")
            if rema < 60:
                rema = rema
            elif rema > 60 and rema < 120:
                rema = rema - 120
            return int(rema)
        else:
            return None
    #### 图像处理--形状识别,目标旋转角度：六边形
    def hexagon_angle(self, approx):
        angle_deg = self.calculate_angle(approx)
        if angle_deg is not None:
            rema = angle_deg % 60
            if rema < 30:
                rema = rema
            elif rema > 30 and rema < 60:
                rema = rema - 60
            return int(rema)
        else:
            return None

   
    #### 图像预处理-- 阈值处理
    def threshold_callback(self,):
        ret, binary = cv2.threshold(self.frame, self.thresh_value, 255, cv2.THRESH_BINARY)
        cv2.imshow('threshold', binary)
        cv2.imshow('ori_image', self.frame)
        while True:
            if cv2.waitKey(1) & 0xFF == ord('q'):  
                break 
        # 关闭窗口
        cv2.destroyWindow('threshold')
        cv2.destroyWindow('ori_image')

    #### 图像预处理--裁剪
    def crop_callback(self,crop_value):
        origin_frame = self.frame.copy()
        crop_image = origin_frame[crop_value[1]:crop_value[3], crop_value[0]:crop_value[2]]
        cv2.rectangle(origin_frame, (crop_value[0], crop_value[1]), (crop_value[2], crop_value[3]), (0, 255, 255), 3)
        cv2.imshow('origin_frame', origin_frame)        
        cv2.imshow('crop_image',crop_image)
        while True:
            if cv2.waitKey(1) & 0xFF == ord('q'):  
                break 
        # 关闭窗口
        cv2.destroyWindow('origin_frame')
        cv2.destroyWindow('crop_image')

    #### 图像预处理--腐蚀
    def Erosion_callback(self):
        origin_frame = self.frame.copy()
        val = self.Erosion_value
        match val:
            case 'kernel_2':
                kernel = np.ones((2, 2), np.uint8)
            case'kernel_3':
                kernel = np.ones((3, 3), np.uint8)
            case'kernel_4':
                kernel = np.ones((4, 4), np.uint8)
          
        erosion = cv2.erode(origin_frame, kernel, iterations=1)
        cv2.imshow('erosion',erosion)  
        while True:
            if cv2.waitKey(1) & 0xFF == ord('q'):  
                break 
        # 关闭窗口
        cv2.destroyWindow('erosion')

    #### 图像预处理--膨胀
    def Dilation_callback(self):
        origin_frame = self.frame.copy()
        val = self.Dilation_value
        match val:
            case 'kernel_2':
                kernel = np.ones((2, 2), np.uint8)
            case'kernel_3':
                kernel = np.ones((3, 3), np.uint8)
            case'kernel_4':
                kernel = np.ones((4, 4), np.uint8)
        dilation = cv2.dilate(origin_frame, kernel, iterations=1)
        cv2.imshow('dilation',dilation)
        while True:
            if cv2.waitKey(1) & 0xFF == ord('q'):  
                break 
        # 关闭窗口
        cv2.destroyWindow('dilation')

    #### 图像处理--颜色识别
    def color_recong(self,frame):
        #### 转换为灰度图像
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        #### 应用高斯模糊
        blurred = cv2.GaussianBlur(gray, (5, 5), 0)
        #### 检测圆形
        circles = cv2.HoughCircles(blurred, cv2.HOUGH_GRADIENT, 1.2, 100)
        if circles is not None:
            for circle_ in circles[0]:
                mask1 = np.zeros_like(frame)
                x, y, r = int(circle_[0]), int(circle_[1]), int(circle_[2])
                inner_radius = r + 10
                #### 绘制大圆
                cv2.circle(mask1, (x, y), inner_radius, (255, 255, 255), -1)
                outer_circle = cv2.bitwise_and(frame, mask1)
                mask1 = np.zeros_like(frame)
                #### 绘制小圆        
                cv2.circle(mask1, (x, y), r, (255, 255, 255), -1)
                inner_circle_inverse = cv2.bitwise_not(mask1)
                annulus_area = cv2.bitwise_and(outer_circle, inner_circle_inverse)
                
                ##### 把BGR图像转换为HSV格式
                HSV = cv2.cvtColor(annulus_area, cv2.COLOR_BGR2HSV)  
                for color in color_list:
                    mask_red = cv2.inRange(HSV, color[0], color[1])
                    #### 腐蚀 膨胀
                    erosion = cv2.erode(mask_red, kernel_2, iterations=1)   
                    dilation = cv2.dilate(erosion, kernel_4, iterations=1)  
                    #### 二值化，将滤波后的图像变成二值图像放在binary中
                    ret, binary = cv2.threshold(dilation, 0, 255, cv2.THRESH_BINARY)  
                    cnts = cv2.findContours(binary.copy(), cv2.RETR_EXTERNAL,
                                            cv2.CHAIN_APPROX_NONE)
                    cnts = imutils.grab_contours(cnts) 
                    self.max_contour(circle_,cnts,color[2])
                    #### 更新位置
                    self.position[color[2]] = [int(circle_[0]), int(circle_[1]), 0]      

    #### 图像处理--形状识别
    def contour_recong(self,frame):
        max_area = 30000
        min_area = 3000
        ###----轮廓检测的第一种方法 切换为HSV通道，进行初步的颜色筛选后，进行轮廓的提取----####
        HSV = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
        color = black
        mask_= cv2.inRange(HSV, color[0], color[1])
        ## 腐蚀  膨胀
        erosion = cv2.erode(mask_, kernel_2, iterations=1)   
        dilation = cv2.dilate(erosion, kernel_4, iterations=1)  
        # 二值化，将滤波后的图像变成二值图像放在binary中
        ret, binary = cv2.threshold(dilation, 0, 255, cv2.THRESH_BINARY) 
        cnts = cv2.findContours(binary.copy(), cv2.RETR_EXTERNAL,
                                cv2.CHAIN_APPROX_SIMPLE)
        contours = imutils.grab_contours(cnts)
        ###----轮廓检测的第二种方法，二值化轮廓提取---start-####
        #### 进行高斯模糊操作
        # blurred = cv2.GaussianBlur(frame, (5, 5), 0)
        # # 进行图片灰度化
        # gray = cv2.cvtColor(blurred, cv2.COLOR_BGR2GRAY)
        # # 进行阈值分割
        # thresh = cv2.threshold(gray, 60, 255, cv2.THRESH_BINARY)[1]
        # # 在二值图片中寻找轮廓
        # contours = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        # contours = imutils.grab_contours(contours)
        ###----轮廓检测的第二种方法，二值化轮廓提取--end--####
        if contours is not None:
            for cnt in contours:  
                # 计算轮廓的边界框  
                x, y, w, h = cv2.boundingRect(cnt)
                contour_area = cv2.contourArea(cnt)
                if contour_area > min_area and contour_area < max_area:
                    cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 3)
                    approx = cv2.approxPolyDP(cnt, 0.016 * cv2.arcLength(cnt, True), True)
                    n_corners = len(approx)
                    # 三角形：三个顶点  
                    if n_corners == 3:
                        angle = self.triangle_angle(approx)
                        M = cv2.moments(cnt)
                        center_tri = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
                        cv2.drawContours(self.frame, [approx], -1, (0, 255, 0), 2)
                        self.position["triangle"] = [center_tri[0], center_tri[1], 0]
               
                    # 正方形：接近正方形的长宽比和四个直角  
                    elif n_corners == 4: 
                        angle = self.angel_count(cnt)
                        if abs(cv2.contourArea(cnt) / ((w*h) ** 0.5)) > 0.95:    
                            M = cv2.moments(cnt)
                            center_square = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
                            cv2.drawContours(self.frame, [approx], -1, (0, 255, 0), 2)
                            self.position["rectangle"] = [center_square[0], center_square[1], angle]

                    # 多边形：
                    elif n_corners >4 and n_corners < 8: 
                        angle = self.hexagon_angle(approx)
                        M = cv2.moments(cnt)
                        center_hexagon = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
                        cv2.drawContours(self.frame, [approx], -1, (0, 255, 0), 2)
                        self.position["hexagon"] = [center_hexagon[0], center_hexagon[1], 0]
    
                    # 圆形：近似为圆形的轮廓  
                    elif n_corners > 7 and n_corners < 100: 
                        angle = self.angel_count(cnt)  
                        M = cv2.moments(cnt)
                        center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
                        cv2.drawContours(self.frame, [approx], -1, (0, 255, 0), 2)
                        self.position["circle"] = [center[0], center[1], angle]

    #### 图像处理--二维码识别
    def QR_recong(self,img):
        # 使用pyzbar解析二维码图片内容
        barcodes = pyzbar.decode(img)
        print(f"borcodes is {barcodes}")
        if barcodes:
            for barcode in barcodes:
                # 提取条形码的边界框的位置
                # 画出图像中条形码的边界框
                (x, y, w, h) = barcode.rect
                cv2.rectangle(self.frame, (x, y), (x + w, y + h), (0, 255, 255), 3)
        
                # 条形码数据为字节对象，所以如果我们想在输出图像上画出来，就需要先将它转换成字符串  类型也直接识别出来了
                barcodeData = barcode.data.decode("utf-8")
                barcodeType = barcode.type
               
                center_x = int(x+w/2)
                center_y = int(y+h/2)
                feature = [center_x,center_y,0]
                print(f"target pixel center is {feature}")

                # 绘出图像上条形码的数据和条形码类型
                text = "{} ({})".format(barcodeData, barcodeType)
                cv2.putText(self.frame, text, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX,.5, (0, 0, 125), 2)     
    
    #### 图像处理--二维码识别
    def ocr_recong(self,img):
        ####设置识别中英文两种语言 need to run only once to load model into memory
        reader = easyocr.Reader(['en'], gpu = False,
                    download_enabled=False,
                    model_storage_directory='./model') 
        ress = reader.readtext(img)
        if ress:
            for res in ress:
                a = res[0][0]
                b = res[0][2]
                center_x = int((a[0]+b[0])/2)
                center_y = int((a[1]+b[1])/2)
                cv2.circle(self.frame, (center_x, center_y), 7, (125, 125, 0), -1)
                feature = [center_x,center_y,0]
                pts = [a,b]
                pts = np.array(pts,np.int32)
                cv2.rectangle(self.frame, pts[0],pts[1], (255, 0, 0), 3)  
                match res[1]:
                    case "A" | "4" | "^" | "Y":
                        self.position["A"] = feature
                    case "B" | "8":
                        self.position["B"] = feature
                    case "C":
                        self.position["C"] = feature
                    case "D" | "0" | "O" | "o":
                        self.position["D"] = feature                           
                print("recongnitioin Character is : %s" %res[1])

    def find_circle_center(self,img,):
        circle_centers = []
        #### 转换为灰度图像
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        #### 应用高斯模糊
        blurred = cv2.GaussianBlur(gray, (5, 5), 0)
        #### 检测圆形
        circles = cv2.HoughCircles(blurred, cv2.HOUGH_GRADIENT, 1.2, 100)
        if circles is not None:
            for circle_ in circles[0]:
                x, y, r = int(circle_[0]), int(circle_[1]), int(circle_[2])
                print(x, y, r)
                cv2.circle(img, (x, y), r, (0, 255, 0), 4)
                cv2.rectangle(img, (x - 5, y - 5), (x + 5, y + 5), (0, 128, 255), -1)
                circle_centers.append((x, y))
        return circle_centers

    def sendPosition(self,cc):
        self.P1 = cc[0]
        self.P2 = cc[1]
        self.P3 = cc[2]
        self.P4 = cc[3]
        self.P5 = cc[4]
        self.P6 = cc[5]
        self.P7 = cc[6]
        self.P8 = cc[7]
        self.P9 = cc[8]
        # self.gui_signal.emit(json.dumps({"source":1,"data":self.P1}))
        # self.gui_signal.emit(json.dumps({"source":2,"data":self.P2}))
        # self.gui_signal.emit(json.dumps({"source":3,"data":self.P3}))
        # self.gui_signal.emit(json.dumps({"source":4,"data":self.P4}))
        # self.gui_signal.emit(json.dumps({"source":5,"data":self.P5}))
        # self.gui_signal.emit(json.dumps({"source":6,"data":self.P6}))
        # self.gui_signal.emit(json.dumps({"source":7,"data":self.P7}))
        # self.gui_signal.emit(json.dumps({"source":8,"data":self.P8}))
        # self.gui_signal.emit(json.dumps({"source":9,"data":self.P9}))

    def getCameraPosition(self,img):
        ## 如果检测到9个点，则发送坐标到UI界面
        while True:
            camera_Coordinate = self.find_circle_center(img)
            cc = camera_Coordinate
            print(f"cc is {cc}")
            if len(cc) == 9:
                self.gui_signal.emit(json.dumps({"source":1,}))
                self.sendPosition(cc)
                break
        # if len(cc) == 9:
        #     self.sendPosition(cc)
        #     self.gui_signal.emit(json.dumps({"source":1,}))
        # else:
        #     self.gui_signal.emit(json.dumps({"source":0,}))

    #### 九点标定,仿射变换矩阵
    def calibrate(self,):
        print(f"start calibrate")
        self.gui_signal.emit(json.dumps({"source":2,}))
        time.sleep(0.01)
        ## 获取相机的9个点坐标
        camera_Coordinate = self.find_circle_center(self.frame)
        result = np.array(camera_Coordinate) 

        camera_Coordinate = '[' + ',\n '.join([str(list(row)) for row in result]) + ']'
        ## 获取机器人的9个点坐标
        robot_Coordinate =self.robot_Coordinate
        matrix, _ = cv2.estimateAffine2D(camera_Coordinate, robot_Coordinate)
        # matrix, mask = cv2.findHomography(camera_Coordinate, robot_Coordinate, cv2.RANSAC, 5.0)
        return matrix

    #### calibrate result
    def calibrate_result(self, matrix):
        print(f"calibrate result is {matrix}")
        self.gui_signal.emit(json.dumps({"source":3,}))
        return matrix

    def run(self,):
        #### 通过CV库函数获取视频流数据
        cap = cv2.VideoCapture(0,)
        cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'))  

        while not self.stop:  
            ret, self.frame = cap.read()  
            if not ret:
                time.sleep(0.1)
                #### 图像一旦获取失败，不断循环获取图像
                cap = cv2.VideoCapture(0,)
                cap.set(cv2.CAP_PROP_FOURCC,cv2.VideoWriter_fourcc('M','J','P','G'))  
                print("Can't to capture from camera. Exiting ...")  
                # break  
            if ret:
                #### 图像裁剪--感兴趣区域进行检测
                # crop = np.zeros([1944,2592,3],np.uint8)
                # crop[crop_x_min:crop_x_max,crop_y_min:crop_y_max] = self.frame[crop_x_min:crop_x_max,crop_y_min:crop_y_max]
                # img = crop
                # print(self.frame.shape)
                # crop = self.frame[crop_x_min:crop_x_max,crop_y_min:crop_y_max]
                img = self.frame

                num = self.program_flag
                match num:
                    #### 图像预处理--二值化处理
                    case 1:
                        self.threshold_callback()
                        time.sleep(0.5)
                        self.program_flag = 0
                        
                    case 2:
                        self.color_recong(img)
                        time.sleep(0.5)
                        self.program_flag = 0
                        
                    case 3:
                        self.contour_recong(img)
                        time.sleep(0.5)
                        self.program_flag = 0
                    
                    case 4:
                        self.QR_recong(img)
                        time.sleep(0.5)
                        self.program_flag = 0
                    
                    case 5:
                        self.ocr_recong(img)
                        time.sleep(0.5)
                        self.program_flag = 0
                    
                    case 6:
                        mat = self.calibrate()
                        self.calibrate_result(mat)
                        time.sleep(0.5)
                        self.program_flag = 0
                    
                    case 7:
                        cv2.imwrite("./img/calibrate_.png",img)
                        img_calibrate = cv2.imread("./img/calibrate_.png")
                        self.getCameraPosition(img_calibrate)
                        self.stop = True
                        time.sleep(0.5)
                        self.program_flag = 0
                    
                    #### 监听程序变量的方式裁剪图像，以下均是
                    case 8:
                        self.crop_callback(self.crop_value)

                    case 9:
                        self.Erosion_callback()
                    case 10:
                        self.Dilation_callback()

                    case _:
                        pass
                time.sleep(0.01)
                #### 图像显示与保存
                self.display()

        
            # 按下'q'键退出循环  
            if cv2.waitKey(1) & 0xFF == ord('q'):  
                break  

        # 释放摄像头资源  
        cap.release()  
        # 关闭所有OpenCV窗口  
        cv2.destroyAllWindows()

    #### 展示图片
    def display(self,):
        try:
            img  = cv2.resize(self.frame,(640,480))
            rgb_img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
            #### 将rgb格式转换为qt可以识别的格式
            img_dis = QImage(rgb_img,rgb_img.shape[1],rgb_img.shape[0],QImage.Format_RGB888)
            #### 加载图片，并设定图片大小
            img_dis = QPixmap(img_dis)
            #### 显示图片,将图片放到label中心
            self.label.setAlignment(QtCore.Qt.AlignCenter) 
            self.label.setPixmap(img_dis)
            time.sleep(0.001)
        except Exception as e:
            logger.error(e,exc_info=True)
    
    ## 获取机器人的9个点坐标
    def cap_camera(self,msg):
        msg = json.loads(msg)
        ### 判断来自哪里的信号
        id = msg.get("id")
        value = msg.get("value")

        match id:
            ### 信号与槽函数的方式裁剪图像
            case 1:
                self.crop_callback(value)
            
            case 2:
                # ### 将字典转换为字符串
                # self.robot_Coordinate_str = " ".join(self.robot_Coordinate.get(i, "") for i in range(1, 10))
                result= self.convert_str_list_to_numpy_array(value)
                self.robot_Coordinate = '[' + ',\n '.join([str(list(row)) for row in result]) + ']'
                print(self.robot_Coordinate)
                return self.robot_Coordinate
    
    def convert_str_list_to_numpy_array(self,str_list):
        # 将字符串列表转换为整数列表
        int_list = [[int(x) for x in s.split(',')] for s in str_list]

        # 将整数列表转换为 NumPy 数组
        np_array = np.array(int_list)

        return np_array



